## Копирование файлов из MS DOS в UNIX.

Пример 30
```cpp
/* /bin/cc -M2 -Ml -DMATCHONLY -LARGE dosfs.c match.c -o dosfs
 * Копирование файлов с дискеты, записанной в MS DOS, в UNIX.
 * Предполагается, что ваша UNIX-машина имеет соответствующий драйвер
 * для чтения дискет, сформатированных на IBM PC.
 * match.c - файл, содержащий текст функции match().
 */
#include <stdio.h>
#include <fcntl.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>

extern char *malloc();  /* выделитель памяти */
extern char *strrchr(); /* поиск последнего вхождения буквы */
extern long lseek();
void readBoot(), readFAT(), readRootDir(), main(), line(), getFile(),
     doDirectory(), mkname(), enterDir(), countFree(), traceclu();

int fd;         /* дескриптор файла - дисковода */

FILE *mapfp;     /* файл трассировки  */
int trace = 0;   /* трассировка пока выключена */
int ask = 1;     /* спрашивать ли подтверждение на перезапись файлов */
int dironly = 0; /* 1: только показывать имена, файлы не скидывать   */

typedef unsigned char  uchar;
/*typedef unsigned short ushort; Есть в sys/types.h */

/* Формат сектора загрузки */
struct boot {
	char jmp[3];      /* команда jmp */
	char label[8];    /* название системы */
	char bfs[2];      /* размер boot-сектора */
	uchar sectorsPerCluster; /* число секторов в кластере */
	char fatoff[2];   /* смещение до начала FAT */
	uchar copies;     /* число копий FAT  */
	char dirsize[2];  /* число записей в корневом каталоге */
	char sectors[2];  /* размер дискеты в секторах */
	uchar desc;       /* описатель типа дискеты */
	char FATsize[2];  /* размер FAT в секторах */
	char sectorsPerTrack[2]; /* число секторов на трек */
	char sides[2];    /* число сторон (1, 2) */
	char hidden[2];   /* число спрятанных секторов */
} *boot;

#define SECTOR 512      /* Размер сектора в байтах   */
int CLU;        /* Размер кластера в байтах          */
int SPC;        /* Размер кластера в секторах        */
int SECT;       /* Число секторов на дискете         */
long capacity;  /* емкость дискеты в байтах          */
ushort MAXCLU;  /* максимальный номер кластера + 1   */

int NDIR;       /* Число слотов в корневом каталоге  */
int DIRSIZE;    /* Длина корневого каталога в байтах */
int ENTRperCLUSTER;  /* Количество слотов в одном кластере каталога */

int SPF;        /* Размер FAT в секторах             */
int FATSIZE;    /* Размер FAT в байтах               */
int FATSTART;   /* Смещение до FAT в байтах          */
int NFAT;       /* Количество копий FAT              */

uchar DESC;     /* Описатель типа дискеты            */
int DATACLU;    /* Начало области данных (номер физич. кластера) */
int bit16 = 0;  /* 1 если FAT использует 16-битные поля, а не 12 */

/* Преобразование char[] в integer */
#define INT(s)  ( * (short *)s)
#define LONG(s) ( * (long  *)s)

/* Формат одной записи каталога. */
struct dir{
	char name[8];   /* имя файла            */
	char ext[3];    /* расширение (суффикс) */
	uchar attrib;   /* атрибуты файла       */
	char unused[10];
	char creat_time[2];     /* время создания */
	char creat_date[2];     /* дата создания  */
	char firstCluster[2];   /* начальный кластер */
	char size[4];           /* размер в байтах */
};
#define isdir(attr)     (attr & 0x10)     /* Является ли каталогом ? */
#define islabel(attr)   (attr & 0x08)     /* Метка тома ?            */

#define eq(s1, s2)      (!strcmp(s1, s2)) /* сравнение строк на ==   */

struct dir *droot;  /* Содержимое корневого каталога */
char *FAT1;         /* File Allocation Table, копия 1 */
char *FAT2;         /*                        копия 2 */
char cwd[256] = "";     /* Текущий каталог в DOS. "" - корневой  */
char *root = "/tmp";    /* Каталог в UNIX, куда копируются файлы */

char *pattern = NULL;   /* шаблон базового имени */
char *dirpattern;       /* каталог (не шаблон)   */

char newname[256];      /* буфер дла генерации имен */
char cluster[4098];     /* буфер для чтения кластера */

/* Чтение n байт по адресу s */
Read(fd, s, n) char *s;
{
	int nn = read(fd, s, n);
	if(nn != n ){
		fprintf(stderr, "Ошибка чтения: %d вместо %d\n", nn, n);
		perror( "read" ); exit(1);
	}
	return nn;
}

/* Позиционирование головок */
long Lseek(fd, off, how) long off;
{
	long offf;
	if((offf = lseek(fd, off, how)) < 0){
		fprintf(stderr, "Ошибка lseek(%ld,%d)\n", off, how);
	}
	return offf;
}

/* Отведение памяти и ее зачистка */
char *Malloc(n) unsigned n;{
	char *ptr = malloc(n);
	register unsigned i;
	if( !ptr){
		fprintf(stderr, "Не могу malloc(%u)\n", n ); exit(2);
	}
	for(i=0; i < n ; i++ ) ptr[i] = 0;
	/* Можно было бы использовать ptr = calloc(1,n); эта функция
	 * как раз отводит и очищает память */
	return ptr;
}

/* Нарисовать горизонтальную черту */
void line(c) char c;{
	register i;
	for(i=0; i < 78; i++) putchar(c);
	putchar('\n');
}

/* Обработка псевдо-имен устройств. Используются имена для XENIX */
char *drive(name) char *name;
{
	if( eq(name, "360")) return "/dev/fd048ds9";
	if( eq(name, "720")) return "/dev/fd096ds9";
	if( eq(name, "1.2")) return "/dev/fd096ds15";
	return name;
}

/* Создать каталог */
char command[512];      /* буфер дла формирования команд */
mkdir(name, mode) char *name;
{
   int retcode;    struct stat st;

   if( stat(name, &st) >= 0 &&
       (st.st_mode & S_IFMT) == S_IFDIR ) return 0; /* уже есть */
   sprintf(command, "mkdir \"%s\"", name );
   retcode = system(command); /* выполнить команду, записанную в command */
   chmod(name, mode & 0777);  /* установить коды доступа */
   return retcode;            /* 0 - успешно */
}

/* Открыть файл, создавая (если надо) недостаюшие каталоги */
FILE *fmdopen(name, mode)
	char *name, *mode;
{
	extern errno;   char *s;    FILE *fp;
	if( fp = fopen(name, mode)) return fp;  /* OK */
	/* иначе файл не смог создаться */
	/* if( errno != ENOENT ) return NULL; /* из-за недостатка прав */
	/* Пробуем создать все каталоги по пути к файлу */
	if((s = strrchr(name, '/' )) == NULL ) return NULL;
	*s = '\0'; md(name); *s = '/';
	return fopen(name, mode);
}

/* Рекурсивный mkdir */
md(path)        char *path;
{       struct stat st; char *s; int code;
	if( !*path) return 0;     /* корневой каталог "/" */
	if( stat(path, &st) >= 0 ){     /* существует */
	    if((st.st_mode & S_IFMT) == S_IFDIR) return 0; /* OK */
	    printf( "%s - не каталог\n", path ); return 1; /* FAIL */
	}
	if( s = strrchr(path, '/')){
	    *s = '\0'; code = md(path); *s = '/';
	    if( code ) return code;     /* Облом */
	}
	sprintf(command, "mkdir \"%s\"", path );
	return system(command); /* 0 если OK */
}

/* Сконструировать имя файла в стиле UNIX.
 * В MS DOS все буквы в именах - большие */
void mkname( res, n, e ) char *res, *n, *e;
{ /* res - результат, n - имя, e - суффикс */
	register i; char *start = res;

	if( n[0] == 0x05 ) n[0] = 0xE5;  /* подставной символ */
	for(i=0; i < 8 && n[i] && n[i] != ' ' ; i++)
		*res++ = n[i];
	if( e[0] != ' ')
		*res++ = '.';
	for(i=0; i < 3 && e[i] && e[i] != ' ' ; i++)
		*res++ = e[i];
	*res = '\0';

	while( *start ){
		if( isalpha(*start) && isupper(*start))
			*start = tolower(*start);
		start++;
	}
}
/* ------------------------------------------------------- */
/* Получить запись из FAT для кластера clu */
ushort numCluster(clu) ushort clu;
{       ushort n;

	if( clu >= MAXCLU )
	  printf( "Слишком большой номер кластера %03X >= %03X\n",
						  clu,    MAXCLU );
	if( bit16 ){    /* 16 бит на номер кластера */
		n = INT( &FAT1[ 2*clu ]);
		n &= 0xFFFF;
		return n;
	} /* иначе 12 бит на номер кластера */
	n = clu + clu/2 ;
	n = INT( &FAT1[n] );
	if( clu % 2 ){  /* нечетный */
		n >>= 4;
	}
	n &= 0xFFF;
	return n;
}

/* Узнать следующий кластер файла. 0 если последний */
ushort nextCluster(clu) ushort clu;
{
	clu = numCluster(clu);
	if( clu >= (bit16 ? 0xFFF8 : 0xFF8 ))
		return 0;       /* EOF */
	return clu;
}

/* Прочесть кластер и сохранить его в файле и буфере */
getCluster(clu, fp, size, buffer)
	ushort clu;     /* логический кластер (2..) */
	FILE *fp;       /* файл для спасения  */
	long size;      /* осталось дописать  */
	char *buffer;   /* буфер для кластера */
{
	long offset;
	int rd, howmuchtoread;

	if( size <= 0L ){
		printf( "CLUSTER %03X лишний\n", clu ); exit(3);
	}
	/* Вычислить смещение. Кластеры нумеруются начиная с #2 */
	offset = (clu - 2 + DATACLU) * (long) CLU;
	Lseek(fd, offset, 0);

	/* Сколько байт прочесть ? */
	howmuchtoread = (size > CLU) ? CLU : size;
	rd = Read(fd, buffer, howmuchtoread);
	if( fp != NULL )
	    fwrite(buffer, 1, rd, fp);
	return ( rd < 0 ) ? 0 : rd;
}
/* ----------------------------------------------------------------- *      dosfs -rPATH    файлы скидываются в каталог PATH, а не в /tmp
 *      dosfs ... "шаблон"    сбрасываются только файлы с подходящими
 *        именами, например:
 *              dosfs 1.2  "/*.c"        *.c из корня дискеты
 *              dosfs 1.2  "/dir1/*.c"   *.c из каталога /dir1
 *              dosfs 1.2  "*.c"         *.c из всех каталогов
 *      dosfs -d        только просмотр каталогов, без сброса файлов
 *      Пример: dosfs -qr. 360
 */
void main(argc, argv) char *argv[];
{
	if( argc < 2 ) goto usage;
	if( *argv[1] == '-' ){  /* разбор ключей */
	    char *keys = &argv[1][1];
	    while(*keys){
	       switch(*keys){
	       case 't':  /* включить трассировку */
		   trace++;
		   if((mapfp = fopen( ".Map", "w" )) == NULL )
		       trace = 0;
		   break;
	       case 'q':  /* без запросов (quiet) */
		   ask = 0; break;
	       case 'r':  /* переназначить root */
		   root = keys+1; goto breakwhile;
	       case 'd':  /* dosfs -d == команда dir */
		   dironly++; break;
	       }
	       keys++;
	    }
	breakwhile:
	    argc--; argv++;
	}
	if( argc < 2 ) goto usage;
	if( pattern = argv[2] ){   /* может быть NULL */
		char *s = strrchr(pattern, '/');
		if(s){  /*      PATH/PATTERN                */
		    dirpattern  = pattern;       /* PATH    */
		    *s = '\0';    pattern = s+1; /* PATTERN */
		}else{  /*      просто PATTERN              */
		    dirpattern = NULL;
		}
	}
	setbuf(stdout, NULL);   /* отменить буферизацию */
	readBoot(drive(argv[1]));
	readFAT();
	countFree();
	readRootDir();
	exit(0);
usage:
	printf( "Вызов:  dosfs  [-dqtrDIR]  устройство [\"шаблон\"]\n" );
	exit(4);
}

/* Прочесть boot-sector, вычислить разные параметры дискеты */
void readBoot(dsk) char *dsk;
{
	char BOOT[SECTOR];
	int skips, sides;

	if((fd = open( dsk, O_RDONLY)) < 0 ){
		fprintf(stderr, "Не могу читать %s\n", dsk); exit(5);
	}
	/* нулевой сектор дискеты - boot */
	Read(fd, BOOT, SECTOR);
	boot = (struct boot *) BOOT;

	line('-');
	printf( "Сформатировано \"%8.8s\"\n", boot->label );
	printf( "Размер boot-сектора %d байт\n", INT(boot->bfs));
	printf( "Кластер содержит %d секторов\n",
		SPC = boot->sectorsPerCluster );
	printf( "Дискета содержит %d секторов ",
		SECT = INT(boot->sectors));
	capacity = SECT * (long) SECTOR;
	printf( "(%ld KB)\n", capacity / 1024L );
	printf( "На треке %d секторов\n", INT(boot->sectorsPerTrack));
	sides = INT(boot->sides);
	printf( "Диск имеет %d сторон%c\n\n", sides, sides==1? 'у':'ы');

	printf( "Смещение до FAT %d сектор\n",
		skips = INT(boot->fatoff));
	printf( "Имеется %d копии FAT\n", NFAT = boot->copies );
	printf( "FAT занимает %d секторов\n\n", SPF = INT(boot->FATsize));

	printf( "Корневой каталог содержит %d записей\n\n",
		NDIR = INT(boot->dirsize));

	printf( "Описатель дискеты = %02X\t(", DESC = boot->desc );
	switch( DESC ){
	case 0xFF: printf( "double sided, 8 sectors per track" ); break;
	case 0xFE: printf( "single sided, 8 sectors per track" ); break;
	case 0xFD: printf( "double sided, 9 sectors per track" ); break;
	case 0xFC: printf( "single sided, 9 sectors per track" ); break;
	case 0xF9: printf( "double sided, 15 sectors per track"); break;
	case 0xF8: printf( "Winchester" ); bit16++; break;
	default:   printf( "неизвестный тип" ); break;
	}
	printf( ")\n");
	printf( "На диске %d спрятанных секторов\n", INT(boot->hidden));

	/* Вычислить характеристики */
	CLU      = SECTOR * SPC;   /* размер кластера в байтах */
	FATSIZE  = SECTOR * SPF;   /* длина FAT в байтах       */
	FATSTART = SECTOR * skips; /* смещение в байтах до FAT */
	/* длина корневого каталога в байтах */
	DIRSIZE  = NDIR   * sizeof(struct dir);
	/* физический номер первого кластера данных */
	DATACLU  = ((long) FATSTART +
		    (long) FATSIZE * NFAT +
		    (long) DIRSIZE ) / CLU;
	printf( "Первый кластер данных (физ.) = %d\n", DATACLU );
	/* число записей каталога в кластере */
	ENTRperCLUSTER = CLU / sizeof(struct dir);

	/* число секторов для данных */
	MAXCLU = (SECT - DATACLU * SPC);
	/* число кластеров для данных */
	MAXCLU = MAXCLU / SPC;
	/* логические номера кластеров идут с #2 */
	MAXCLU += 2;
}

/* Прочесть File Allocation Table (таблицу размещения файлов) */
void readFAT(){
	register int i;

	FAT1 = Malloc(FATSIZE);

	Lseek(fd, (long) FATSTART, 0);
	Read(fd, FAT1, FATSIZE);
	if(NFAT > 1){
		FAT2 = Malloc(FATSIZE);
		Read(fd, FAT2, FATSIZE);

		/* Сравнить копии FAT */
		for(i=0; i < FATSIZE; i++ )
			if(FAT1[i] != FAT2[i]){
			   printf( "копии FAT различаются в %d/%d\n",
				    i, FATSIZE );
			   break;
			}
		free( FAT2 );
	}
	if( DESC != FAT1[0] )
	    printf( "У FAT другой описатель: %02X\n", FAT1[0] & 0xFF );
}

/* Прочесть корневой каталог дискеты.
 * Он расположен сразу же после копий FAT
 */
void readRootDir(){
	if( DIRSIZE % SECTOR )
		printf( "Размер каталога не кратен сектору\n" );
	Lseek(fd, (long)FATSTART + (long)FATSIZE * NFAT, 0);
	droot = (struct dir *) Malloc(DIRSIZE);
	Read(fd, droot, DIRSIZE );
	/* NDIR должно быть 112 для 360K и 720K
	 *                  224 для 1.2 Mb
	 */
	if( !dironly ) mkdir( root, 0755 );
	line('-');
	doDirectory(0, NDIR, droot);
}

/* Обработать каталог (напечатать, спасти файлы, обойти подкаталоги) */
#define PRINT  \
  for(j=0; j < level; j++ ) printf( "  " ); /* отступ */                \
  printf( "%02d\t%s/%-14s   %12ld   %s\n",                              \
	   strt + i,                                                    \
		 cwd,                                                   \
		    basename,                                           \
			    size,                                       \
				    isdir(dd[i].attrib) ?    "<DIR>"  : \
				    islabel(dd[i].attrib) ?  "<LAB>"  : "" )

void doDirectory(strt, entries, dd)
	struct dir dd[];
{
	register i, j;
	char basename[40];
	static int level = 0;
	int need_to_get;        /* надо ли сбрасывать */

	/* line('-'); */
	for(i=0; i < entries; i++ ){
	   uchar c; long size;

	   if((c = *dd[i].name) == 0xE5 || !c)
		   continue;        /* файл стерт (дыра) */
	   mkname(basename, dd[i].name, dd[i].ext);
	   size = LONG(dd[i].size); /* размер файла */

	   /* проверить шаблон имени, если нужно */
	   if( !pattern          || /* pattern задан и */
	       (   (!dirpattern  || eq(cwd, dirpattern)) &&
		   match(basename, pattern)
	       )
	   ){  PRINT; need_to_get = !dironly; }
	   else       need_to_get = 0;

	   if(isdir(dd[i].attrib)){
	       /* себя и родителя проигнорировать */
	      if( eq(basename, "." ) || eq(basename, ".."))
		   continue;
	       level++; /* У каталогов почему-то size == 0 */
		enterDir( basename, INT(dd[i].firstCluster), need_to_get);
	       level--;
	   } else if( islabel(dd[i].attrib)){
	       printf( "Volume label:%11.11s\n", dd[i].name );
	   } else if( need_to_get )
	       getFile ( basename, INT(dd[i].firstCluster), size);
	}
	/* line('#'); */
}

/* Прочесть файл в UNIX-ную файловую систему */
void getFile(name, clu, size)
	char *name;     /* имя файла */
	ushort clu;     /* начальный кластер */
	long size;      /* размер */
{
	FILE *fp;       /* файл куда сохранять */
	struct stat st;
	ushort nclu = 0;/* порядковый номер кластера */

	sprintf(newname, "%s%s/%s", root, cwd, name );

	if( ask && stat(newname, &st) >= 0 ){
		char answer[30];
		fprintf(stderr, "%s уже существует, перезаписать? ",
				 newname);
		gets(answer);
		if( *answer != 'y' ) return;
		fprintf( stderr, "\tOK\n" );
	}
	if((fp = fmdopen( newname, "w" )) == NULL){
		printf( "Не могу создать %s\n", newname );
		return;
	}
	if( trace ) fprintf( mapfp, "\n%s/%s:", cwd, name );

	while( clu ){
		if( trace ) traceclu(nclu++, clu);
		size -= getCluster(clu, fp, size, cluster);
		clu = nextCluster(clu);
	}
	fclose(fp);
}

/* Обработать подкаталог */
void enterDir(name, clu, create)
	char *name;     /* имя */
	ushort clu;     /* начальный кластер */
{
	char *tail, *myCluster;
	struct dir *dsub;
	ushort nclu;
	int nentries;   /* число записей в каталоге */

	/* Коррекция cwd */
	tail = cwd + strlen(cwd);
	*tail = '/'; strcpy(tail+1, name);

	if( create ){   /* создать */
	    sprintf( newname, "%s%s", root, cwd );
	    mkdir  ( newname, 0755);
	}
	if( trace ) fprintf( mapfp, "\nDIR %s:", cwd);

	myCluster = Malloc( sizeof cluster );
	dsub = (struct dir *) myCluster;

	nentries = nclu = 0;
	while( clu ){
		if( trace ) traceclu(nclu++, clu);
		/* Прочесть очередной кластер каталога */
		getCluster(clu, NULL,(long) CLU, myCluster);
		/* Обработать имена в этом кластере */
		doDirectory(nentries, ENTRperCLUSTER, dsub);
		nentries += ENTRperCLUSTER;
		/* Взять следующий кластер */
		clu = nextCluster(clu);
	}
	*tail = '\0';   free(myCluster);
}

/* Подсчет свободных и плохих кластеров. */
void countFree(){
	int isFree = 0;       /* свободные кластеры */
	int isBad  = 0;       /* сбойные кластеры   */
	int isReserved = 0;   /* спрятанные кластеры */

	register ushort n = 0;
	register ushort clu;  /* текущий анализируемый кластер */
	int nline = 300;

	if( trace ) fprintf(mapfp, "\t\tFAT chart\n");
	for(clu=0; clu < MAXCLU; clu++){
		if( clu >= 2 ){
		    n = numCluster(clu);
		    if( n == 0 ) isFree++;
		    if( n == (bit16 ? 0xFFF7 : 0xFF7)) isBad++;
		    if( n >= (bit16 ? 0xFFF0 : 0xFF0 ) &&
			n <  (bit16 ? 0xFFF7 : 0xFF7 )) isReserved++;
		}
		if( trace ){
		  if( nline >= 8){
			nline = 0; fprintf( mapfp, "\n%03X:\t", clu );
		  } else  nline++;
		  fprintf( mapfp, "%03X ", n );
		}
	}
	line('=');
	printf( "Свободно %ld, испорчено %ld, резерв %d кластеров\n",
		      (long)isFree * CLU,  /* в байтах */
			       (long)isBad * CLU,   isReserved );
}

void traceclu(nclu, clu) ushort nclu, clu;
{
	if( nclu % 16 == 0 )
	    fprintf( mapfp, "\n\t" );
	fprintf( mapfp, "%03X ", clu );
}

#ifdef LOCAL_MALLOC
/*
Обратите внимание, что в этой программе память отводится malloc()
и освобождается free() по принципу стека (LIFO).
Мы могли бы переопределить стандартные функции malloc() и free(),
заставив их работать со статической памятью! (Если мы напишем
свою функцию с именем, как у стандартной, то будет использоваться
НАША функция).
*/
static char allocArena[32 * 1024];
static char *top = allocArena;
char *malloc(n){        char *ptr;
	/* округлить до целого числа слов */  /* деление с остатком */
	/* число int-ов: */  n = (n + (sizeof(int)-1)) / sizeof(int);
	/* число char-ов:*/  n *= sizeof(int);
	ptr = top; top += n; return ptr;
}
free(ptr) char *ptr; { top = ptr; }
#endif /*LOCAL_MALLOC*/

 
	/*      Пример 31      */
/* Интроспективная программа: печатает сама себя */

#include <stdio.h>
char *text[] = {
	"#include <stdio.h>",
	"char *text[] = {",
	"        NULL};",
	"/* Программа, печатающая свой собственный текст */",
	"main(){ int i;",
	"  puts(text[0]); puts(text[1]);",
	"  for(i=0; text[i]; i++) putq(text[i]);",
	"  for(i=2; text[i]; i++) puts(text[i]);",
	"}",
	"putq(s) char *s; {",
	"  printf(\"\\t\\\"\");",
	"  while(*s){",
	"    if(*s == '\"')       printf(\"\\\\\\\"\");",
	"    else if(*s == '\\\\') printf(\"\\\\\\\\\");",
	"    else putchar(*s);",
	"    s++;",
	"  }",
	"  printf(\"\\\",\\n\");",
	"}",
        NULL};
/* Программа, печатающая свой собственный текст */
main(){ int i;
  puts(text[0]); puts(text[1]);
  for(i=0; text[i]; i++) putq(text[i]);
  for(i=2; text[i]; i++) puts(text[i]);
}
putq(s) char *s; {
  printf("\t\"");
  while(*s){
    if(*s == '"')       printf("\\\"");
    else if(*s == '\\') printf("\\\\");
    else putchar(*s);
    s++;
  }
  printf("\",\n");
}
 
	/*      Пример 32     */
/* C beautify: программа cb.c, форматирующая исходный
 * текст программы на Си. Текст взят из дистрибутива UNIX */
#include <stdio.h>
#include <stdlib.h>

#define gets    getlex
#define puts    putlex

	/* прототипы */
void main(int argc, char *argv[]);
void ptabs( void );
int getch( void );
void puts( void );
int lookup( char *tab[] );
int gets( void );
void gotelse( void );
int getnl( void );
void comment( void );

int     slevel[10];
int     clevel  = 0;
int     spflg[20][10];
int     sind [20][10];
int     siflev[10];
int     sifflg[10];
int     iflev   = 0;
int     ifflg   = -1;
int     level   = 0;
int     ind[10] = { 0,0,0,0,0,0,0,0,0,0 };
int     eflg    = 0;
int     paren   = 0;
int     pflg[10] = { 0,0,0,0,0,0,0,0,0,0 };
char    lchar;
char    pchar;
int     aflg    = 0;
int     ct;
int     stabs[20][10];
int     qflg    = 0;
char    *wif[] = { "if",NULL};
char    *welse[] = { "else", NULL};
char    *wfor[] =  { "for" , NULL};
char    *wds[] =   { "case","default", NULL};
int     j       = 0;
char    string[200];
char    cc;
int     sflg    = 1;
int     peek    = -1;
int     tabs    = 0;
int     lastchar;
int     c;

void main(int argc, char *argv[])
{
	if( argc > 1 ){
		if( freopen( argv[1], "r", stdin ) == NULL ){
			fprintf(stderr, "Can't open %s\n", argv[1] );
			exit(1);
		}
	}
	if( argc > 2 ){
		if( freopen( argv[2], "w", stdout ) == NULL ){
			fprintf(stderr, "Can't create %s\n", argv[2] );
			exit(1);
		}
	}
	while((c = getch()) != EOF){
		switch(c){
		case ' ':
		case '\t':
			if(lookup(welse) == 1){
				gotelse();
				if(sflg == 0 || j > 0) string[j++] = c;
				puts();
				sflg = 0;
				if(getnl() == 1){
					puts();
					printf("\n");
					sflg = 1;
					pflg[level]++;
					tabs++;
				}
				continue;
			}
			if(sflg == 0 || j > 0) string[j++] = c;
			continue;
		case '\n':
			if((eflg = lookup(welse)) == 1) gotelse();
			puts();
			printf("\n");
			sflg = 1;
			if(eflg == 1){
				pflg[level]++;
				tabs++;
			}
			else
				if(pchar == lchar)
					aflg = 1;
			continue;
		case '{':
			if(lookup(welse) == 1) gotelse();
			siflev[clevel] = iflev;
			sifflg[clevel] = ifflg;
			iflev = ifflg = 0;
			clevel++;
			if(sflg == 1 && pflg[level] != 0){
				pflg[level]--;
				tabs--;
			}
			string[j++] = c;
			puts(); getnl(); puts(); printf("\n");
			tabs++;
			sflg = 1;
			if(pflg[level] > 0){
				ind[level] = 1;
				level++;
				slevel[level] = clevel;
			}
			continue;
		case '}':
			clevel--;
			if((iflev = siflev[clevel]-1) < 0) iflev = 0;
			ifflg = sifflg[clevel];
			if(pflg[level] >0 && ind[level] == 0){
				tabs -= pflg[level];
				pflg[level] = 0;
			}
			puts();
			tabs--;
			ptabs();
			if((peek = getch()) == ';'){
				printf("%c;", c);
				peek = -1;
			}
			else printf("%c", c);
			getnl(); puts(); printf("\n");
			sflg = 1;
			if(clevel < slevel[level])if(level > 0) level--;
			if(ind[level] != 0){
				tabs -= pflg[level];
				pflg[level] = 0;
				ind[level] = 0;
			}
			continue;
		case '"':
		case '\'':
			string[j++] = c;
			while((cc = getch()) != c){
				string[j++] = cc;
				if(cc == '\\'){
					string[j++] = getch();
				}
				if(cc == '\n'){
					puts();
					sflg = 1;
				}
			}
			string[j++] = cc;
			if(getnl() == 1){
				lchar = cc;
				peek = '\n';
			}
			continue;
		case ';':
			string[j++] = c;
			puts();
			if(pflg[level] > 0 && ind[level] == 0){
				tabs -= pflg[level];
				pflg[level] = 0;
			}
			getnl(); puts(); printf("\n");
			sflg = 1;
			if(iflev > 0)
				if(ifflg == 1){
					iflev--; ifflg = 0;
				}
				else iflev = 0;
			continue;
		case '\\':
			string[j++] = c;
			string[j++] = getch();
			continue;
		case '?':
			qflg = 1;
			string[j++] = c;
			continue;
		case ':':
			string[j++] = c;
			if(qflg == 1){
				qflg = 0;
				continue;
			}
			if(lookup(wds) == 0){
				sflg = 0;
				puts();
			}
			else{
				tabs--; puts(); tabs++;
			}
			if((peek = getch()) == ';'){
				printf(";");
				peek = -1;
			}
			getnl(); puts(); printf("\n");
			sflg = 1;
			continue;
		case '/':
			string[j++] = c;
			if((peek = getch()) != '*') continue;
			string[j++] = peek;
			peek = -1;
			comment();
			continue;
		case ')':
			paren--;
			string[j++] = c;
			puts();
			if(getnl() == 1){
				peek = '\n';
				if(paren != 0) aflg = 1;
				else if(tabs > 0){
					pflg[level]++;
					tabs++;
					ind[level] = 0;
				}
			}
			continue;
		case '#':
			string[j++] = c;
			while((cc = getch()) != '\n') string[j++] = cc;
			string[j++] = cc;
			sflg = 0;
			puts();
			sflg = 1;
			continue;
		case '(':
			string[j++] = c;
			paren++;
			if(lookup(wfor) == 1){
				while((c = gets()) != ';');
				ct=0;
cont:
				while((c = gets()) != ')'){
					if(c == '(') ct++;
				}
				if(ct != 0){
					ct--; goto cont;
				}
				paren--;
				puts();
				if(getnl() == 1){
					peek = '\n';
					pflg[level]++;
					tabs++;
					ind[level] = 0;
				}
				continue;
			}
			if(lookup(wif) == 1){
				puts();
				stabs[clevel][iflev] = tabs;
				spflg[clevel][iflev] = pflg[level];
				sind[clevel][iflev]  = ind[level];
				iflev++;
				ifflg = 1;
			}
			continue;
		default:
			string[j++] = c;
			if(c != ',') lchar = c;
		}
	}
}

void ptabs( void ){
	int i;
	for(i=0; i < tabs; i++) printf("\t");
}

int getch( void ){
	if(peek < 0 && lastchar != ' ' && lastchar != '\t')
	   pchar = lastchar;
	lastchar = (peek<0) ? getc(stdin) : peek;
	peek = -1;
	return(lastchar);
}

void puts( void ){
	if(j > 0){
		if(sflg != 0){
			ptabs();
			sflg = 0;
			if(aflg == 1){
				aflg = 0;
				if(tabs > 0) printf("    ");
			}
		}
		string[j] = '\0';
		printf("%s",string);
		j = 0;
	}
	else{
		if(sflg != 0){
			sflg = 0; aflg = 0;
		}
	}
}

int lookup( char *tab[] )
{
	char r;
	int l,kk,k,i;
	if(j < 1) return(0);
	kk=0;
	while(string[kk] == ' ') kk++;
	for(i=0; tab[i] != 0; i++){
		l=0;
		for(k=kk;(r = tab[i][l++]) == string[k] && r != '\0';k++);
		if(r == '\0' &&
		   (string[k] < 'a' || string[k] > 'z' || k >= j))
		      return(1);
	}
	return(0);
}

int gets( void ){
	char ch;
beg:
	if((ch = string[j++] = getch()) == '\\'){
		string[j++] = getch();
		goto beg;
	}
	if(ch == '\'' || ch == '"'){
		while((cc = string[j++] = getch()) != ch)
		     if(cc == '\\') string[j++] = getch();
		goto beg;
	}
	if(ch == '\n'){
		puts();
		aflg = 1;
		goto beg;
	}
	else return(ch);
}

void gotelse( void ){
	tabs = stabs[clevel][iflev];
	pflg[level] = spflg[clevel][iflev];
	ind[level]  = sind [clevel][iflev];
	ifflg = 1;
}

int getnl( void ){
	while((peek = getch()) == '\t' || peek == ' '){
		string[j++] = peek;
		peek = -1;
	}
	if((peek = getch()) == '/'){
		peek = -1;
		if((peek = getch()) == '*'){
			string[j++] = '/';
			string[j++] = '*';
			peek = -1;
			comment();
		}
		else string[j++] = '/';
	}
	if((peek = getch()) == '\n'){
		peek = -1;
		return(1);
	}
	return(0);
}

void comment( void ){
rep:
	while((c = string[j++] = getch()) != '*')
		if(c == '\n'){
			puts();
			sflg = 1;
		}
gotstar:
	if((c = string[j++] = getch()) != '/'){
		if(c == '*') goto gotstar;
		goto rep;
	}
}
